package detector.audio.processing;

import java.io.*;
import java.util.*;

import org.apache.http.client.ClientProtocolException;

import detector.audio.ServerComm;
import edu.washington.cs.sounddetector.server.api.exception.DetectorDoesNotExistException;
import edu.washington.cs.sounddetector.server.api.exception.SoundClassDoesNotExistException;
import edu.washington.cs.sounddetector.server.api.exception.SoundDetectorInternalErrorException;

import android.app.Activity;
import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Color;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListAdapter;

/**
 * 
 * Class for storing a single "class", i.e. a sound a user is trying to detect.
 * Each SoundClass has a name and a set of example ids that belong to it. Those
 * example ids refer to audio training data on the server.
 * 
 * This class guarantees that every instance of SoundClass you access is
 * accessible statically (i.e. from getSounds()) (For example, when you load
 * sounds from file, it'll also add them to this class's internal set of
 * SoundClasses).
 * 
 * This class uses a factory to make sure only 1 sound with a given name exists
 * at a time, and also to make serialization easy
 * 
 * @author Kyle
 * 
 */
public class SoundClass implements Serializable {

    // TODO: save to file instead of relying on server
    //NOTE: Do updates in separate thread/in bulk. Like how gMail does it,
    //instead of doing server communication and waiting every time a change is made,
    //since that is slow and doesn't scale. Android has a sync API
    
    //NOTE: Could make the static stuff its own singleton, it's mashed together
    // with the SoundClass here
	/**
	 * 
	 */
	private static final long serialVersionUID = -5394579180580705235L;

	private static final int DEFAULT_COLOR = Color.GRAY;

	public static final Object UKNOWN_CLASS_NAME = "unknown";

	private static final String TAG = "SoundClass";

	private String name;
	private int color;

	// stores the sound classes
	private transient static Set<SoundClass> sounds;

	private List<Long> exampleIds; // the examples for this sound

	// Private constructor
	private SoundClass(String name, int color) {
		this.name = name;
		this.color = color;
		exampleIds = new ArrayList<Long>();
	}

	/**
	 * 
	 * @return all sound classes that have been loaded/added, including the SoundClass representing the uknown
	 * class
	 */
	public static HashSet<SoundClass> getSounds() {
		checkNull();
		HashSet<SoundClass> result = new HashSet<SoundClass>();
		for (SoundClass sc : sounds) {
			result.add(sc);
		}
		return result;
	}

	/**
	 * Adds the example ids to this soundclass. If the exampleId already exists
	 * for this soundClass, don't add it.
	 */
	public void addExampleIds(List<Long> exampleIds) {
		checkNull();
		for (Long id : exampleIds) {
			if (!this.exampleIds.contains(id))
				this.exampleIds.add(id);
		}
	}

	/**
	 * 
	 * @return the ids of audio clips on the server that are examples of this
	 *         soundclass. The returned list is unmodifiable.
	 */
	public List<Long> getExampleIds() {
		return Collections.unmodifiableList(exampleIds);
	}

	/**
	 * Creates the sound in the SoundClass. It will be saved to file and loaded
	 * every time the app is run..
	 * 
	 * @param name
	 *            name of the sound
	 * @return an existing sound if a sound by this name already exists,
	 *         otherwise a new sound by the passed name
	 */
	public static SoundClass getSoundClass(String name) {
		checkNull();
		SoundClass res = findSound(name);
		if (res == null) {
			res = new SoundClass(name, DEFAULT_COLOR);
			sounds.add(res);
		}
		return res;
	}
	
	/**
	 * 
	 * @return true iff this soundclass represents an uknown class - a class
	 * that could not be identified by the server
	 */
	public boolean isUknown() {
		return this.name.equals(UKNOWN_CLASS_NAME);
	}

	public int getColor() {
		return color;
	}

	/**
	 * Also updates it on the server
	 * @param color
	 */
	public void setColor(int color) {
		this.color = color;
		try {
			ServerComm.setColor(this);
		} catch (Exception ex) {
			Log.e(TAG,Log.getStackTraceString(ex));
		}
	}

	@Override
	public boolean equals(Object o) {
		boolean result = (o instanceof SoundClass);

		return result && ((SoundClass) o).name.equals(name);
	}

	@Override
	public int hashCode() {
		if (name != null)
			return name.hashCode();
		else
			return "".hashCode();
	}

	/**
	 * Removes all sounds from the soundclass
	 */
	public static void clear() {
		checkNull();
		sounds.clear();
	}

	// When reading, save the read objects to the internal set if it doesn't
	// exist already.
	// Do NOT use this with getSerializableExtra
	private void readObject(ObjectInputStream in) throws IOException,
			ClassNotFoundException {
		checkNull();
		// instantiate using the info on the input
		in.defaultReadObject();
		// Only add it if it doesn't already exist
		sounds.add(this);
	}

	/**
	 * Special hook provided by serialization where developer can control what
	 * object needs to sent. However this method is invoked on the new object
	 * instance created by de serialization process.
	 * 
	 * @return
	 * @throws ObjectStreamException
	 */
	private Object readResolve() throws ObjectStreamException {
		// We implement this because we use a object pool pattern,
		// we don't want calling classes to obtain multiple references to 
		// sound classes using the serialization
		return SoundClass.getSoundClass(this.name);
	}

	// checks if sounds is null and if so,
	// initializes it
	private static void checkNull() {
		if (sounds == null) {
			sounds = new HashSet<SoundClass>();
		}
	}

	@Override
	public String toString() {
		return name;
	}

	/**
	 * 
	 * @return name of the soundclass
	 */
	public String getName() {
		return name;
	}

	// find the soundclass in the set, null if not found
	private static SoundClass findSound(String name) {
		checkNull();
		// FUTURE: Maybe a dictionary would be better than
		// searching through the list
		for (SoundClass sc : sounds) {
			if (sc.name.equals(name))
				return sc;
		}
		return null;
	}

	/**
	 * Removes the passed soundclass from the static set of soundclasses, if it
	 * exists. Otherwise, does nothing
	 * 
	 * @param sc
	 *            SoundClass to remove
	 */
	public static void deleteSound(SoundClass sc) {
		// no change occurred
		sounds.remove(sc);
	}
}
